<?php

/**
 * Maikuro CMS
 *
 * LICENSE:
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * The license is bundled with this package in the file LICENSE.txt.
 * You may also obtain a copy of the License at this URL:
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @copyright  Copyright (c) 2010, Jonathan Hedrén
 * @author     Jonathan Hedrén <jonathan.hedren@gmail.com>
 * @license    http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
 */

/**
 * Inspired by the way Textpattern (http://www.textpattern.com) and Radiant CMS
 * (http://www.radiantcms.org) handles templating.
 */
class TagParser
{

    /**
     * @var array;
     */
    protected $tags = array();

    /**
     *
     * @param array $matches
     * @return array
     */
    protected function attributesToArray($matches)
    {
        $params = array();
        if (preg_match_all('/(\w+)\s*=\s*"([^"]*)"(?:\s|$)|(\w+)\s*=\s*\'([^\']*)\'(?:\s|$)|(\w+)\s*=\s*([^\s\'"]+)(?:\s|$)/', $matches, $match, PREG_SET_ORDER)) {
            foreach ($match as $m) {
                if (!empty($m[1])) {
                    $params[strtolower($m[1])] = stripcslashes($m[2]);
                } else if (!empty($m[3])) {
                    $params[strtolower($m[3])] = stripcslashes($m[4]);
                } else if (!empty($m[5])) {
                    $params[strtolower($m[5])] = stripcslashes($m[6]);
                }
            }
        }
        return $params;
    }

    /**
     * Enter description here...
     *
     * @param boolean $boolean
     * @return boolean
     */
    protected function invertStatement($boolean)
    {
        return $boolean ? false : true;
    }


    /**
     *
     * @param string $output
     * @return string
     */
    public function parse($input)
    {

        $pattern = '@<(/?)m:(\w+)((?:\s+\w+\s*=\s*"(?:[^"]|"")*")*)(\s*/?)>@s';
        $chunks = preg_split($pattern, $input, -1, PREG_SPLIT_DELIM_CAPTURE);
        $chunks = array_chunk($chunks, 5);

        $output = '';

        $currentTag = null;
        $statements = array(true);

        foreach ($chunks as $chunk) {
            $statementDepth = count($statements) - 1;
            $lastStatement = $statements[$statementDepth];

            if ($lastStatement) {
                $output .= $chunk[0];
            }

            $endTag = false;
            if (!empty($chunk[1])) {
                $endTag = true;
            }

            if (isset($chunk[2])) {
                $tagAndMethod = $chunk[2];
            } else {
                continue;
            }

            $parts = explode('_', $tagAndMethod);
            $tag = array_shift($parts);

            if ($tag != 'if' && $tag != 'else' && !isset($this->tags[$tag])) {
                throw new Exception('There is no tag "' . $tag . '" registered.');
            }

            if ($tag == 'if' && !$endTag) {
                $invert = false;

                if (!$lastStatement) {
                    $statements[] = false;
                    continue;
                }

                $tag = array_shift($parts);
                if ($tag == 'not') {
                    $tag = array_shift($parts);
                    $invert = true;
                }

                $tagInstance = $this->tags[$tag];

                $statement = true;

                if (count($parts)) {

                    $tagMethod = 'if';
                    foreach ($parts as $part) {
                        $tagMethod .= ucfirst($part);
                    }
                    if (isset($chunk[3]) && $chunk[3] != "") {
                        $params = $this->attributesToArray($chunk[3]);
                        $statement = $tagInstance->$tagMethod($params);
                    } else {
                        $statement = $tagInstance->$tagMethod();
                    }

                } else {
                    $statement = $tagInstance->evaluate();
                }

                if ($invert) {
                    $statement = $this->invertStatement($statement);
                }

                $statements[] = $statement;

            } else if ($tag == 'else' && $statements[$statementDepth - 1]) {

                $statements[$statementDepth] = $this->invertStatement($lastStatement);

            } else if ($tag == 'if' && $endTag) {

                array_pop($statements);

            } else if ($lastStatement) { # We are in a positive closure

                if (isset($chunk[1]) && $chunk[1] != "") {

                    $output .= $currentTag->getHtmlCloseTag();
                    $currentTag = null;

                } else {

                    $tagInstance = $this->tags[$tag];

                    $params = array();
                    if (isset($chunk[3]) && $chunk[3] != "") {
                        $params = $this->attributesToArray($chunk[3]);
                    }

                    if (count($parts)) {
                        $tagMethod = 'display';
                        foreach ($parts as $part) {
                            $tagMethod .= ucfirst($part);
                        }
                        $output .= $tagInstance->$tagMethod($params);
                    } else {
                        $output .= $tagInstance->display($params);
                    }

                    if (empty($chunk[4])) {
                        $currentTag = $tagInstance;
                    }
                }
            }
        }

        if (count($statements) > 1) {
            throw new Exception('Missing end statement');
        }

        return $output;
    }

    /**
     *
     *
     * @param string $tagName
     * @param $tagInstance
     * @return TagParser
     */
    public function registerTag($tagName, $tagInstance)
    {
        $this->tags[$tagName] = $tagInstance;
        return $this;
    }
}